package com.boot.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.boot.entity.Attachment;
import com.boot.entity.ImageInfo;
import com.boot.entity.Question;
import com.boot.mapper.AttachmentMapper;
import com.boot.mapper.ImageInfoMapper;
import com.boot.service.IAttachmentService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.boot.service.IQuestionService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * <p>
 * 附件表，用于存储与问题关联的上传文件信息 服务实现类
 * </p>
 *
 * @author bbyh
 * @since 2025-10-31
 */
@Service
public class AttachmentServiceImpl extends ServiceImpl<AttachmentMapper, Attachment> implements IAttachmentService {
    @Resource
    private IQuestionService questionService;


    @Value("${file.uploadDir}")
    private String uploadDirStr;

    @Value("${img.uploadDir}")
    private String imgDirStr;

    private Path uploadDir;

    private Path imgDir;

    @PostConstruct
    public void init() {
        uploadDir = Paths.get(uploadDirStr);
        if (!Files.exists(uploadDir)) {
            try {
                Files.createDirectories(uploadDir);
            } catch (IOException e) {
                throw new RuntimeException("无法创建上传目录: " + uploadDirStr, e);
            }
        }

        imgDir = Paths.get(imgDirStr);
        if (!Files.exists(imgDir)) {
            try {
                Files.createDirectories(imgDir);
            } catch (IOException e) {
                throw new RuntimeException("无法创建上传目录: " + imgDirStr, e);
            }
        }
    }

    @Override
    public List<Attachment> listAttachmentsByQuestionId(Long questionId) {
        QueryWrapper<Question> questionQueryWrapper = new QueryWrapper<>();
        questionQueryWrapper.eq("id", questionId);
        Question question = questionService.getOne(questionQueryWrapper);
        if (question == null) {
            throw new RuntimeException("问题不存在");
        }

        QueryWrapper<Attachment> wrapper = new QueryWrapper<>();
        wrapper.eq("question_id", questionId)
                .select("id", "file_name"); // 只查这两个字段
        return this.list(wrapper);
    }

    @Override
    public void streamDownloadAttachmentsAsZip(Long questionId, OutputStream outputStream) throws IOException {
        List<Attachment> attachments = this.list(new QueryWrapper<Attachment>().eq("question_id", questionId));
        if (attachments.isEmpty()) {
            throw new FileNotFoundException("该问题下没有附件");
        }

        try (ZipOutputStream zos = new ZipOutputStream(outputStream)) {

            // 添加每个附件
            for (Attachment att : attachments) {
                Path filePath = uploadDir.resolve(att.getFileKey());
                if (!Files.exists(filePath)) {
                    continue;
                }
                addFileToZip(zos, filePath, "content/" + att.getFileName());
            }

            // 添加安全提醒文件
            String warningText =
                    "⚠️ 安全提醒：此压缩包中的文件由用户上传。\n\n" +
                            "可能包含恶意代码（如 Python 脚本），请勿随意执行！\n\n" +
                            "建议操作：\n" +
                            "- 查看源码内容前，请先审查是否有可疑命令（如 os.system, subprocess, eval 等）\n" +
                            "- 不要直接运行 .py/.sh 等脚本文件\n" +
                            "- 若不确定安全性，请勿打开\n\n" +
                            "平台无法保证所有文件绝对安全，请自行判断风险。\n";

            ZipEntry readmeEntry = new ZipEntry("README_SECURITY.txt");
            zos.putNextEntry(readmeEntry);
            zos.write(warningText.getBytes(StandardCharsets.UTF_8));
            zos.closeEntry();

            zos.finish(); // 必须调用，确保所有数据写出
            // 注意：不要关闭 zos，因为它是包装在 outputStream 上的，真正关闭由外层处理
        }
    }

    private void addFileToZip(ZipOutputStream zos, Path path, String entryName) throws IOException {
        ZipEntry entry = new ZipEntry(entryName);
        zos.putNextEntry(entry);

        try (InputStream is = Files.newInputStream(path)) {
            byte[] buffer = new byte[8192];
            int bytesRead;
            while ((bytesRead = is.read(buffer)) != -1) {
                zos.write(buffer, 0, bytesRead);
            }
        }

        zos.closeEntry();
    }

    @Resource
    private ImageInfoMapper imageInfoMapper;

    @Override
    public ResponseEntity<StreamingResponseBody> serveImage(String imageName) throws Exception {
        if (imageName == null || imageName.trim().isEmpty()) {
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null);
        }

        // 解码防止中文乱码
        String decodedImageName = URLDecoder.decode(imageName, "UTF-8");

        // 查询数据库获取真实存储路径
        ImageInfo imageInfo = imageInfoMapper.selectOne(
                new QueryWrapper<ImageInfo>().eq("image_name", decodedImageName)
        );

        if (imageInfo == null) {
            return ResponseEntity.status(HttpStatus.NOT_FOUND)
                    .contentType(MediaType.TEXT_PLAIN)
                    .body(out -> out.write("图片不存在".getBytes(StandardCharsets.UTF_8)));
        }

        // 组合完整物理路径
        Path filePath = imgDir.resolve(imageInfo.getStoragePath());
        Path normalized = filePath.normalize();

        // 安全检查：防止路径穿越
        if (!normalized.startsWith(imgDir.normalize())) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).body(null);
        }

        if (!Files.exists(normalized)) {
            return ResponseEntity.status(HttpStatus.NOT_FOUND)
                    .contentType(MediaType.TEXT_PLAIN)
                    .body(out -> out.write("图片文件丢失".getBytes(StandardCharsets.UTF_8)));
        }

        if (!Files.isRegularFile(normalized)) {
            return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
        }

        // 推断 Content-Type
        String contentType = Files.probeContentType(normalized);
        if (contentType == null || !contentType.startsWith("image/")) {
            contentType = "image/octet-stream";
        }

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.parseMediaType(contentType));
        headers.setContentLength(Files.size(normalized));

        StreamingResponseBody body = outputStream -> {
            byte[] buffer = new byte[8192];
            int bytesRead;
            try (InputStream in = Files.newInputStream(normalized)) {
                while ((bytesRead = in.read(buffer)) != -1) {
                    outputStream.write(buffer, 0, bytesRead);
                }
                outputStream.flush();
            }
        };

        return ResponseEntity.ok()
                .headers(headers)
                .body(body);
    }
}
